﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Reflection;

using RAD.LightOne.Data;
using RAD.LightOne;
using RAD.LightOne.Cache;

namespace RAD.LightOne
{
    #region Documentation
    /// <summary>
    /// Classe responsável por extrair os metadados das instâncias e/ou tipos que herdem de MyObject.
    /// </summary>
    #endregion
    public static class MetadataReader
    {
        #region  Internal Methods

        #region Documentation
        /// <summary>
        /// Extrai todos os elementos marcados com DBField.
        /// </summary>
        #endregion
        internal static IEnumerable<MemberInfo>  ExtractDBFieldMarked(Type startType)
        {
            if (!(startType.IsSubclassOf(typeof(MyObject)))) throw new ArgumentException("startType don't inherits MyObject.");

            Type[] types = MetadataReader.GetHierarchy(startType, typeof(MyObject));

            List<MemberInfo> dbFields = new List<MemberInfo>();

            BindingFlags binding = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;

            foreach (Type type in types)
            {
                #region Comments
                /*
                 * Neste trecho de código, eu realizo uma varredura nos campos e propriedades à 
                 * busca de membros marcados com DBField, os quais vão compondo a lista que será passada
                 * a um DBTableItem.
                 */
                #endregion Comments

                FieldInfo[] fields = type.GetFields(binding);
                foreach (FieldInfo field in fields)
                {
                    if (DBFieldAttribute.IsDefined(field))
                    {
                        dbFields.Add(field);
                    }
                }
                PropertyInfo[] properties = type.GetProperties(binding);
                foreach (PropertyInfo property in properties)
                {
                    if (DBFieldAttribute.IsDefined(property))
                    {
                        dbFields.Add(property);
                    }
                }
            }

            return dbFields;

        }

        #region Documentation
        /// <summary>
        /// Mais leve que "ExtractStructuredData", retorna apenas um array de string com os nomes das tabelas
        /// da hierarquia.
        /// </summary>
        /// <param name="type">tipo que será vasculhado.</param>
                #endregion Documentation
        internal static string[] ExtractTables(Type type)
        {
            List<string> tables = new List<string>();
            foreach (Type t in GetHierarchy(type, typeof(MyObject)))
            {
                if (DBTableAttribute.IsDefined(t))
                {
                    DBTableAttribute attribute = ((DBTableAttribute[])t.GetCustomAttributes(typeof(DBTableAttribute), false))[0];
                    tables.Add(attribute.TableName);
                }
            }

            return tables.ToArray();
        }

        #region Documentation
        /// <summary>
        /// Retorna a primeira tabela da hierarquia de relacionamento para herança de classes.
        /// </summary>
        /// <param name="type">tipo a ser vasculhado até a tabela.</param>
        /// <returns>nome da tabela</returns>
        #endregion Documentation
        internal static string ExtractMainTable(Type type)
        {
            foreach (Type t in GetHierarchy(type, typeof(MyObject)))
            {
                if (DBTableAttribute.IsDefined(t))
                {
                    DBTableAttribute attribute = ((DBTableAttribute[])t.GetCustomAttributes(typeof(DBTableAttribute), false))[0];
                    return attribute.TableName;
                }
            }

            throw new InvalidOperationException("The type passed don't have table in your hierarchy");
        }

        #region Documentation
        /// <summary>
        /// Método com a função de retornar um array de tipos ordenado por sua ordem hierárquica.
        /// </summary>
        /// <param name="startType">Tipo do qual se inicia a busca. Classe específica de onde se inicia a busca até o limite definido por stopType.</param>
        /// <param name="stopType">Tipo ao qual atingido na busca, esta cessa.</param>
        #endregion Documentation
        internal static Type[] GetHierarchy(Type startType, Type stopType)
        {
            Stack<Type> stack = new Stack<Type>();
            GetHierarchy(startType, stopType, ref stack);
            Type[] types = stack.ToArray();
            return types;
        }

        #region Documentation
        /// <summary>
        /// Método recursivo interno para varredura da hierarquia de tipos.
        /// </summary>
        #endregion Dcoumentation
        private static void GetHierarchy(Type startType, Type stopType, ref Stack<Type> stack)
        {
            if (stopType == startType)
            {
                stack.Push(startType);
                return;
            }

            stack.Push(startType);
            GetHierarchy(startType.BaseType, stopType, ref stack);
        }

        #region Documentation
        /// <summary>
        /// Realiza uma busca recursiva, para trás, a partir de um tipo até encontrar um tipo marcado com o atributo
        /// DBTable.
        /// </summary>
        /// <param name="type">Tipo a ser analizado, inicial da busca.</param>
        /// <returns>tipo que marcado com DBTable.</returns>
        #endregion
        internal static Type RewardSeekDBTableDefinedType(Type type)
        {
            if (type == typeof(MyObject)) return null;
            if (DBTableAttribute.IsDefined(type))
            {
                return type;
            }
            else
            {
                return RewardSeekDBTableDefinedType(type.BaseType);
            }
        }

        #region Documentation
        /// <summary>
        /// Retorna a(s) tabela(s) significativa(s) para identificação de uma classe no contexto geral do banco de dados
        /// quando seu tipo real não pôde ser definido.
        /// </summary>
        /// <param name="type">Tipo a ser analizado.</param>
        /// <returns>lista de nomes de tabelas de 1 a n nomes.</returns>
        #endregion
        internal static List<string> GetSignTables(Type type)
        {
            //if (!type.IsSubclassOf(typeof(MyObject))) throw new System.Reflection.TargetException("This type in ivalid to do this operation, MyObjectMetadataReader.GetSignTables.");
            if (!MyObject.IsMyObject(type)) throw new System.Reflection.TargetException("This type in ivalid to do this operation, MyObjectMetadataReader.GetSignTables.");

            List<string> tables = new List<string>();

            Type markedType = MetadataReader.RewardSeekDBTableDefinedType(type);
            if (markedType != null)
            {
                tables.Add(((DBTableAttribute[])markedType.GetCustomAttributes(typeof(DBTableAttribute), false))[0].TableName);
            }
            else
            {
                List<Type> allTypes = new List<Type>();
                allTypes.AddRange(new TypesPool());
                foreach (Type t in allTypes)
                {
                    if (t.IsSubclassOf(type))
                    {
                        if (DBTableAttribute.IsDefined(t))
                        {
                            tables.Add(((DBTableAttribute[])t.GetCustomAttributes(typeof(DBTableAttribute), false))[0].TableName);
                        }
                    }
                }
            }

            return tables;
        }

        #region Documentation
        /// <summary>
        /// Retorna  a consulta sql que deve ser utilizada para se localizar o tipo real de um tipo passado como parâmetro.
        /// O tipo passado deve obrigatóriamente ser abstrato como fomra de limitar a utilização deste método em
        /// detrimento de outros de melor perfomance.
        /// </summary>
        /// <param name="type">tipo alvo</param>
        /// <returns>stringBuilder contendo a expressão sql e também uma entrada "{CLAUSULE}" em seu interior que deve ser substituida pelo Id da 
        /// classe a ser encontrada.</returns>
        /// <exception cref="TargetException">Exception disparada caso não seja passado um tipo absrato como parâmetro.</exception>
        /// <exception cref="InvalidOperatinException">Exception disparada quando nenhuma tabela é encontrada na hierarquia do tipo.</exception>
        /// <example>retorna uma stringbuilder contendo: "Select id, class_name From tabela Where id={CLAUSULE}"</example>
        #endregion
        internal static StringBuilder GetDiscoverTypeQuery(Type type)
        {
            #region Comments
            /*
             * Método que se utiliza também do cache de expressões sql, as expressões geradas/buscadas por ele
             * têm sempre o prefixo "Discover." seguido do fullName do tipo.
             */
            #endregion
            if (!type.IsAbstract) throw new System.Reflection.TargetException("This method can be used by abstract types only.");

            const string prefix = "Discover.";
            const string sqlBase = "{0}SELECT id, class_name FROM {1} WHERE id={CLAUSULE}";

            Cache.SelectQueryCache cache = SelectQueryCache.Instance();
            if (cache.Contains(prefix + type.FullName)) return cache[prefix + type.FullName];

            List<string> tables = MetadataReader.GetSignTables(type);
            if (tables.Count == 0) throw new InvalidConstraintException("The current assembly don't have a table in this type hyerarchy.");

            StringBuilder sql = new StringBuilder();

            for (int i = 0; i < tables.Count; i++)
            {
                if (i == 0)
                {
                    sql.AppendFormat(sqlBase, "", tables[i]);
                }
                else
                {
                    sql.AppendFormat(sqlBase, " UNION ", tables[i]);
                }
            }

            KeyValuePair<string,StringBuilder> cahceItem = new KeyValuePair<string,StringBuilder>(prefix + type.FullName , sql);
            return sql;
        }

        #region Documentation
        /// <summary>
        /// Retorna o tipo real de um tipo "irreal" passado baseado no id da instância do objeto.
        /// </summary>
        /// <param name="type">tipo "irreal" passado e base de busca.</param>
        /// <param name="id">id da instância do objeto.</param>
        /// <returns>Tipo real do objeto.</returns>
        #endregion Documentation
        internal static Type GetRealTypeOf(Type type, Guid id, IDbTransaction trx)
        {
            DBEngine dbe = DBEngine.GetInstance(trx);

            dbe.CommandText = MetadataReader.GetDiscoverTypeQuery(type).Replace("{CLAUSULE}", id.ToString()).ToString();

            IDataParameter param = dbe.CreateParameter();
            param.ParameterName = new Field("id").GetParameterName();
            param.Value = id.ToString();
            dbe.Parameters.Add(param);

            try
            {
                using (IDataReader reader = dbe.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        return new TypesPool()[Convert.ToString(reader[Global.CLASS_NAME])];
                    }
                    else
                    {
                        throw new NullReferenceException("MyObjectMetadataReader.GetRealTypeOf causes exception by id that not addressing a real type.");
                    }
                }

            }
            catch (Exception except)
            {
                throw new InvalidOperationException("MyObjectMetadataReader.GetRealTypeOf causes exception on data base operations.", except);
            }
        }

        #region Documentation
        /// <summary>
        /// Retorna todos os campos marcados de um tipo em toda a extensão de sua hierarquia abaixo e a partir dele mesmo, 
        /// de acordo com a operação selecionada, independentemente de haver sido marcados "campos" ou "propriedades".
        /// </summary>
        /// <param name="startType">Tipo alvo da extração.</param>
        /// <returns>lista de nomes de campos.</returns>
        #endregion Documentation
        internal static List<string> ExtractAllFieldsOnHyerarchy(Type startType)
        {
            if (!(startType.IsSubclassOf(typeof(MyObject)))) throw new ArgumentException("startType don't inherits MyObject.");

            Type[] types = MetadataReader.GetHierarchy(startType, typeof(MyObject));

            List<string> dbFields = new List<string>();

            BindingFlags binding = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;

            foreach (Type type in types)
            {
                #region Comments
                /*
                 * Neste trecho de código, eu realizo uma varredura nos campos e propriedades à 
                 * busca de membros marcados com DBField, os quais vão compondo a lista que será passada
                 * a um DBTableItem.
                 */
                #endregion Comments

                FieldInfo[] fields = type.GetFields(binding);
                foreach (FieldInfo field in fields)
                {
                    if (DBFieldAttribute.IsDefined(field))
                    {
                        DBFieldAttribute attrib = (DBFieldAttribute)field.GetCustomAttributes(typeof(DBFieldAttribute), false)[0];
                        dbFields.Add(attrib.FieldName);
                    }
                }
                PropertyInfo[] properties = type.GetProperties(binding);
                foreach (PropertyInfo property in properties)
                {
                    if (DBFieldAttribute.IsDefined(property))
                    {
                        DBFieldAttribute attrib = (DBFieldAttribute)property.GetCustomAttributes(typeof(DBFieldAttribute), false)[0];
                        dbFields.Add(attrib.FieldName);
                    }
                }
            }

            return dbFields;
        }

        #region Documentation
        /// <summary>
        /// Retorna todos os campos que sejam MyObjects da herarquia de tipos do tipo passado no parâmetro.
        /// </summary>
        #endregion
        internal static FieldInfo[] GetMyObjectFields(Type type)
        {
            List<FieldInfo> fInfos = new List<FieldInfo>();
            foreach(Type t in GetHierarchy(type,typeof(MyObject)))
            {
                foreach (FieldInfo fInfo in t.GetFields(Global.BINDING_FLAGS))
                {
                    if (MyObject.IsMyObject(fInfo.FieldType))
                    {
                        fInfos.Add(fInfo);
                    }
                }
            }

            return fInfos.ToArray();
        }

        #region Documentation
        /// <summary>
        /// Retorna tabelas e campos-chave relacionados.
        /// </summary>
        #endregion
        internal static Dictionary<Table,Dictionary<KeyField,string>> GetTableAndKeyFieldNames(Type type)
        {
            Dictionary<Table, Dictionary<KeyField,string>> items = new Dictionary<Table, Dictionary<KeyField,string>>();
            foreach (Type t in GetHierarchy(type, typeof(MyObject)))
            {
                if (DBTableAttribute.IsDefined(t))
                {
                    DBTableAttribute attribute = ((DBTableAttribute[])t.GetCustomAttributes(typeof(DBTableAttribute), false))[0];
                    Table table = new Table(attribute.TableName);
                    items.Add(table, new Dictionary<KeyField,string>());

                    foreach (var attrib in FieldNameAttribute.GetDefinedAttributes(t))
                    {
                        items[table].Add(attrib.Key, attrib.FieldName);
                    }

                    /*IdAttribute id = IdAttribute.GetDefinedAttribute(t);
                    items[table].Add(id.Key, id.FieldName);
                    ClassNameAttribute clsName = ClassNameAttribute.GetDefinedAttribute(t);
                    items[table].Add(clsName.Key, clsName.FieldName);
                    LastPersistenceAttribute lastP = LastPersistenceAttribute.GetDefinedAttribute(t);
                    items[table].Add(lastP.Key, lastP.FieldName);*/

                }
            }

            return items;
        }
        #endregion

        #region Public Methods

        #region Documentation
        /// <summary>
        /// Método que retorna um objeto Field, que contém metadados refletidos diretamente do banco de dados e ainda as operações de dados
        /// modeladas. Case insensitive.
        /// </summary>
        /// <typeparam name="T">Tipo genérico, derivado de MyObject que será pesquisado.</typeparam>
        /// <param name="fieldName">Nome do campo/propriedade que terá seus metadados refletidos e retornados.</param>
        /// <returns>Metadados.</returns>
        #endregion
        public static Field GetFieldSchema<T>(string fieldName) where T : MyObject
        {
            InternalObj obj = InternalObjBuilder.GetReadStructure(typeof(T), DataOperations.All);

            foreach (Table tItem in obj)
            {
                foreach (Field fItem in tItem)
                {
                    if (string.Compare(fItem.NameOnObject, fieldName, true) == 0)
                    {
                        DataTable schema = SchemaHelper.GetTableSchema(tItem.Name);
                        fItem.Lenght = schema.Columns[fItem.Name].MaxLength;
                        fItem.AllowNull = schema.Columns[fItem.Name].AllowDBNull;
                        return fItem.Clone() as Field;
                    }
                }
            }
            return null;
        }

        #endregion
    }
}
